// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2025 Datadog, Inc.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log/slog"
	"os"
	"sort"
	"strings"

	"github.com/DataDog/dd-trace-go/v2/internal/env"
	"github.com/dave/jennifer/jen"
)

func main() {
	var (
		input  string
		output string
	)
	flag.StringVar(&input, "input", "./internal/env/supported_configurations.json", "Path to the input file")
	flag.StringVar(&output, "output", "./internal/env", "Path to the output directory")
	flag.Parse()

	// Get command and keys from positional arguments
	args := flag.Args()
	if len(args) == 0 {
		slog.Error("no command provided")
		os.Exit(1)
	}

	command := args[0]

	var err error
	switch command {
	case "generate":
		err = generate(input, output)
	case "check":
		err = check(input)
	case "add":
		err = add(input, output, args[1:])
	default:
		err = fmt.Errorf("unknown command: %s", command)
	}

	if err != nil {
		slog.Error("error executing command", "error", err)
		os.Exit(1)
	}
	slog.Info("success executing command", "command", command)
}

type supportedConfiguration struct {
	SupportedConfigurations map[string][]string `json:"supportedConfigurations"`
	Aliases                 map[string][]string `json:"aliases"`
}

// generate generates the supported configurations map from the supported configurations
// JSON file.
func generate(input, output string) error {
	cfg, err := getSupportedConfigurations(input)
	if err != nil {
		return fmt.Errorf("error getting supported configuration keys: %w", err)
	}

	keys := sortSupportedConfigurationsKeys(cfg)

	slog.Info("supported configuration keys count", "count", len(keys))

	f := jen.NewFilePath(output)
	f.HeaderComment("Unless explicitly stated otherwise all files in this repository are licensed")
	f.HeaderComment("under the Apache License Version 2.0.")
	f.HeaderComment("This product includes software developed at Datadog (https://www.datadoghq.com/).")
	f.HeaderComment("Copyright 2025 Datadog, Inc.")
	f.Comment("Code generated by github.com/DataDog/dd-trace-go/v2/scripts/configinverter. DO NOT EDIT.")
	f.Line()
	f.Comment("SupportedConfigurations is a map of supported configuration keys.")
	f.Var().Id("SupportedConfigurations").Op("=").Map(jen.String()).Struct().ValuesFunc(func(g *jen.Group) {
		for _, v := range keys {
			g.Add(jen.Line(), jen.Lit(v), jen.Op(":"), jen.Values())
		}
		g.Line()
	})
	f.Line()

	// Get aliases keys and sort them
	// so we can generate the map in a deterministic way
	aliases := []string{}
	for k := range cfg.Aliases {
		aliases = append(aliases, k)
	}
	sort.Strings(aliases)

	f.Comment("keyAliases maps aliases to supported configuration keys.")
	f.Var().Id("keyAliases").Op("=").Map(jen.String()).Index().String().ValuesFunc(func(g *jen.Group) {
		for _, v := range aliases {
			g.Add(jen.Line(), jen.Lit(v), jen.Op(":"), jen.ValuesFunc(func(g *jen.Group) {
				for _, v := range cfg.Aliases[v] {
					g.Add(jen.Lit(v))
				}
			}))
		}
		g.Line()
	})

	if err := f.Save(output + "/supported_configurations.gen.go"); err != nil {
		return fmt.Errorf("error saving file: %w", err)
	}

	return nil
}

// check verifies that there is no difference between the supported configurations
// JSON file and generated Go code map.
func check(input string) error {
	keys, err := getSupportedConfigurationsKeys(input)
	if err != nil {
		return fmt.Errorf("error getting supported configuration keys: %w", err)
	}

	slog.Info("supported configuration keys in JSON file", "count", len(keys))
	slog.Info("supported configuration keys in generated map", "count", len(env.SupportedConfigurations))

	missingKeys := []string{}
	for _, k := range keys {
		if _, ok := env.SupportedConfigurations[k]; !ok {
			slog.Error("supported configuration key not found in generated map", "key", k)
			missingKeys = append(missingKeys, k)
		}
	}

	if len(missingKeys) > 0 {
		slog.Error("supported configuration keys missing in generated map", "count", len(missingKeys), "keys", missingKeys)
		slog.Info("run `go run ./scripts/configinverter/main.go generate` to re-generate the supported configurations map with the missing keys")
		return fmt.Errorf("supported configuration keys missing in generated map")
	}

	slog.Info("supported configurations JSON file and generated map are in sync")

	return nil
}

// add adds new keys to the supported configurations JSON file and re-generates the
// supported configurations map using the `generate` command.
func add(input, output string, newKeys []string) error {
	cfg, err := getSupportedConfigurations(input)
	if err != nil {
		return fmt.Errorf("error getting supported configuration keys: %w", err)
	}

	filteredKeys := []string{}
	for _, k := range newKeys {
		// Filter out keys that don't start with DD_ or OTEL_
		if strings.HasPrefix(strings.ToUpper(k), "DD_") || strings.HasPrefix(strings.ToUpper(k), "OTEL_") {
			filteredKeys = append(filteredKeys, strings.ToUpper(k))
		}
	}

	missingKeys := []string{}
	for _, k := range filteredKeys {
		_, ok := cfg.SupportedConfigurations[k]
		if !ok {
			missingKeys = append(missingKeys, k)
		} else {
			slog.Info("key already exists", "key", k)
		}
	}

	if len(missingKeys) == 0 {
		slog.Info("no new keys to add")
		return nil
	}

	slog.Info("adding missing keys", "count", len(missingKeys), "keys", missingKeys)

	err = addSupportedConfigurationsKeys(input, cfg, missingKeys)
	if err != nil {
		return fmt.Errorf("error adding supported configuration keys to file: %w", err)
	}

	if err := generate(input, output); err != nil {
		return fmt.Errorf("error running generate command after adding supported configuration keys: %w", err)
	}

	return nil
}

// getSupportedConfigurationsKeys gets the supported configurations keys from the JSON file.
// It returns a sorted list of env variables.
func getSupportedConfigurationsKeys(input string) ([]string, error) {
	cfg, err := getSupportedConfigurations(input)
	if err != nil {
		return nil, fmt.Errorf("error getting supported configuration: %w", err)
	}

	return sortSupportedConfigurationsKeys(cfg), nil
}

func sortSupportedConfigurationsKeys(cfg *supportedConfiguration) []string {
	keys := []string{}
	for k := range cfg.SupportedConfigurations {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	return keys
}

// getSupportedConfigurations gets the supported configurations from the JSON file.
func getSupportedConfigurations(input string) (*supportedConfiguration, error) {
	fcontent, err := os.ReadFile(input)
	if err != nil {
		return nil, fmt.Errorf("error getting supported configuration keys from embed: %w", err)
	}

	slog.Info("read file", "file", input)

	var cfg supportedConfiguration
	if err := json.Unmarshal(fcontent, &cfg); err != nil {
		return nil, fmt.Errorf("error unmarshalling supported configuration: %w", err)
	}

	return &cfg, nil
}

// addSupportedConfigurationsKeys adds new keys to the supported configurations JSON
// file.
func addSupportedConfigurationsKeys(input string, cfg *supportedConfiguration, newKeys []string) error {
	for _, k := range newKeys {
		cfg.SupportedConfigurations[k] = []string{"A"}
	}

	json, err := json.MarshalIndent(cfg, "", "  ")
	if err != nil {
		return fmt.Errorf("error marshalling supported configuration: %w", err)
	}

	return os.WriteFile(input, json, 0644)
}
